#include"verilated.h"
#include"Vysyx_22040213_top.h"
#include"stdio.h"
#include"stdlib.h"
#include"assert.h"
#include"verilated_vcd_c.h"
#include"verilated_fst_c.h"
#include"svdpi.h"
#include "verilated_dpi.h"
#include <cpu/ifetch.h>
#include <cpu/decode.h>
#include <utils.h>
#include <myconfig.h>
#include <cpu/difftest.h>
#include <cpu/cpu.h>
#include <common.h>
#include <utils.h>
#include <device/alarm.h>
#include <SDL2/SDL.h>
void vga_update_screen();

void device_update() {
  static uint64_t last = 0;
  uint64_t now = get_time();
  if (now - last < 10000 / 60) {
	  return;
  }
  last = now;

  if(HAS_VGA)
  {
	  vga_update_screen();
  };

  SDL_Event event;
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
      case SDL_QUIT:
        npc_state.state = NEMU_QUIT;
        break;
  
	default: break;
    }
  }
}

//simulation related

#define MAX_INST_TO_PRINT 9
VerilatedContext* contextp = NULL;
VerilatedFstC* wavePtr = NULL;

static Vysyx_22040213_top* top;
void single_cycle(){
	extern uint64_t g_nr_guest_inst;
	top->clk = 0;top->eval();
//	contextp->timeInc(1);
//	tfp->dump(contextp->time());

	top->clk = 1;top->eval();
//	if(g_nr_guest_inst % 1000000 == 0 && top->o_reg_valid)printf("your core has run %ld cycles\n",g_nr_guest_inst);
#ifdef WAVE_DUMP
	if(g_nr_guest_inst > WAVE_DUMP_LOW && g_nr_guest_inst < WAVE_DUMP_HIGH){
//	  contextp->timeInc(1);
	  wavePtr->dump(g_nr_guest_inst);
 	}
#endif
	
}


static void reset(int n){
	top->rst = 1;
	while (n -- >0) single_cycle();
	top->rst = 0;
}

void sim_init(){
	contextp = new VerilatedContext;
	top = new Vysyx_22040213_top;
	wavePtr = new VerilatedFstC;
#ifdef WAVE_DUMP
	contextp->traceEverOn(true);
	top->trace(wavePtr,1);
	wavePtr->open("obj_dir/sim.wave");
	wavePtr->dump(0);
#endif

}

void sim_exit(){
single_cycle();
wavePtr->close();
}
/*MY CPU*/
#ifdef IRINGBUF
#define IRINGBUF_LEN 32
typedef struct {
	int index;
	char buf[IRINGBUF_LEN][128];
}IRingBuf;

IRingBuf iringbuf;
#endif

uint64_t isa_reg_str2val(const char *s, bool *success); 
void isa_reg_display();
void difftest_skip_ref();
void difftest_skip_dut(int nr_ref, int nr_dut);

riscv64_CPU_state cpu = {};
bool success = true;
bool check_wp();
uint64_t g_nr_guest_inst = 0;
static bool g_print_step = false;
static bool npc_trap = false;
static int reg_delay = 0;
//static int id_bubble = 0;
//static int bubble_num = 0;
int callnum = 0;
int jumpcall;
int retcall;  
extern "C" void NPCTRAP (){
	npc_trap = true;
}

extern "C" void ftrace (){
	jumpcall = 1;
}
extern "C" void NPCEXCEPTION (){
	
}


static void trace_and_difftest(Decode *_this, vaddr_t dnpc) {
#ifdef ITRACE_COND
  if (ITRACE_COND) { log_write("%s\n", _this->logbuf); }
#endif
  if (g_print_step) { IFDEF(ITRACE, puts(_this->logbuf)); }
#ifdef DIFFTEST
 
  reg_delay += top->o_reg_valid;
/*  if(top->id_bubble || id_bubble > 0){
    printf("id_bubble!\n");
    id_bubble += top->WB_valid_out;
    if(top->id_bubble){
     bubble_diff += top->id_bubble;
    }
  }
  if(id_bubble == 6){
    printf("skip for bubble\n");
    reg_delay = 0;
    id_bubble -= 6;
    bubble_num--;
  }*/
  //valid till wb_valid and skip when id bubble and exe bubble
//  printf("top->inst:%x,pc:%lx,id_bubble%d,exe_bubble:%d\n",top->o_reg_inst,top->o_reg_dnpc,top->o_reg_id_bubble,top->o_reg_exe_bubble);
  if(top->o_reg_id_bubble || top->o_reg_exe_bubble ){
    reg_delay = 0;
  }
  if((top->o_reg_devices_access) && top->o_reg_valid){
    difftest_skip_ref();
//    printf("devices skip at pc:%lx\n",top->o_reg_dnpc);
  }
  if(reg_delay > 0 && top->o_reg_valid){
   difftest_step(_this->pc, top->o_reg_dnpc);
//   printf("skip at %lx\n",top->pc);
  }
  else{
//    printf("wait for reg valid\n");
  }


#endif

#ifdef WATCHPOINT
  if(check_wp())npc_state.state = NEMU_STOP;
#endif
}

static void statistic() {
/*  IFNDEF(CONFIG_TARGET_AM, setlocale(LC_NUMERIC, ""));
#define NUMBERIC_FMT MUXDEF(CONFIG_TARGET_AM, "%ld", "%'ld")
  Log("host time spent = " NUMBERIC_FMT " us", g_timer);
  Log("total guest instructions = " NUMBERIC_FMT, g_nr_guest_inst);
  if (g_timer > 0) Log("simulation frequency = " NUMBERIC_FMT " inst/s", g_nr_guest_inst * 1000000 / g_timer);
  else Log("Finish running in less than 1 us and can not calculate the simulation frequency");*/
}

void cpu_init(){
	sim_init();
	reset(10);
}
extern "C" void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte);

static void exec_once(Decode *s, vaddr_t pc){
	jumpcall = 0;
//	top->inst = inst_fetch(top->pc,4);
	top->valid_in = 1;
	single_cycle();
	s->pc   = pc;
	s->snpc = pc;
	cpu.pc  = top->pc;
	s->dnpc = top->dnpc;
//	printf("top->pc:%lx,s->dnpc:%lx\n",top->pc,s->dnpc);
//	printf("inst:%08x\n",inst_fetch(top->pc,4));
//	s->inst = top->instt;
	s->inst = inst_fetch(s->pc,4);
#ifdef ITRACE
  char *p = s->logbuf;
  p += snprintf(p, sizeof(s->logbuf), FMT_WORD ":", top->o_reg_dnpc);
  int ilen = 4;
  int i;
 // uint32_t instt = inst_fetch(top->pc,4);
  uint32_t instt = top->inst;
  uint8_t *inst = (uint8_t *)&instt;
  for (i = 0; i < ilen; i ++) {
	//printf("%02x\n",inst[i]);
	p += snprintf(p, 4, " %02x", inst[i]);
  }
//  printf("here\n");
  int ilen_max = 4;
  int space_len = ilen_max - ilen;
  if (space_len < 0) space_len = 0;
  space_len = space_len * 3 + 1;
  memset(p, ' ', space_len);
  p += space_len;

  disassemble(p, s->logbuf + sizeof(s->logbuf) - p, top->o_reg_dnpc, (uint8_t *)&top->o_reg_inst, ilen);
#endif
#ifdef FTRACE
if(s->inst == 0x00008067) retcall = 1;
  if(jumpcall){
    printf("pc:0x%08lx:",s->pc);
      if(!retcall){
        for(int n = 0; n < callnum;n++)printf(" ");
	  callnum++;
	  for(int i = 0;i<mysym.ncount;i++){
	    if(s->dnpc >= mysym.value[i] && s->dnpc < (mysym.value[i]+mysym.size[i]-4)){
	      printf("call(%s@%08lx)\n",mysym.name[i],s->dnpc);
	    }
   	  }
	}
      else{
	callnum--;
	retcall = 0;
	for(int n = 0; n < callnum;n++)printf(" ");
	for(int i = 0;i<mysym.ncount;i++){
	  if(s->dnpc >= mysym.value[i] && s->dnpc < (mysym.value[i]+mysym.size[i]-4)){
	    printf("ret(%s@%08lx)\n",mysym.name[i],s->dnpc);
	  }
	}
      }
  }
#endif

}

static void execute(uint64_t n){
       	Decode s;
	for(;n > 0; n--){
		g_nr_guest_inst ++;
		exec_once(&s, cpu.pc);
		trace_and_difftest(&s,s.dnpc);
		if(npc_trap){
/*		  exec_once(&s, cpu.pc);
	  	  exec_once(&s, cpu.pc);
		  exec_once(&s, cpu.pc);
		  exec_once(&s, cpu.pc);
		  exec_once(&s, cpu.pc);*/
		   top->valid_in = 0;
   		   single_cycle(); //wb finished
		   single_cycle(); //exe finished
		   single_cycle(); //id finished
	  	  npc_state.state = NEMU_END;
	  	  npc_state.halt_pc = top->pc - 12;
	  	  npc_state.halt_ret = isa_reg_str2val("a0",&success);
		}
#ifdef IRINGBUF
		if(iringbuf.index<IRINGBUF_LEN && top->o_reg_valid){
			strcpy(iringbuf.buf[iringbuf.index],s.logbuf);
		}
		else if(top->o_reg_valid){
			for(int n=0;n<IRINGBUF_LEN-1;n++){
				strcpy(iringbuf.buf[n], iringbuf.buf[n+1]);
			}
			strcpy(iringbuf.buf[IRINGBUF_LEN-1],s.logbuf);
		}
		iringbuf.index++;
#endif
		if(npc_state.state != NEMU_RUNNING){
		 break;
		}
		if(HAS_DEVICE){ 
		  device_update();
		};
	}
}


void cpu_exec(uint64_t n){
	extern uint64_t g_nr_guest_inst;
	g_print_step = (n < MAX_INST_TO_PRINT);
	switch (npc_state.state) {
		case NEMU_END: case NEMU_ABORT:
      		Log("Program execution has ended. To restart the program, exit NEMU and run again.\n");return;
		default: npc_state.state = NEMU_RUNNING;
  }
#ifdef IRINGBUF
	iringbuf.index= 0;
#endif
	execute(n);
	switch (npc_state.state) {
		case NEMU_RUNNING: npc_state.state = NEMU_STOP; break;
		case NEMU_END: case NEMU_ABORT:
		  Log("MYnpc: %s at pc = " FMT_WORD,
		(npc_state.state == NEMU_ABORT ? ASNI_FMT("ABORT", ASNI_FG_RED) :
		(npc_state.halt_ret == 0 ? ASNI_FMT("HIT GOOD TRAP", ASNI_FG_GREEN) :
                ASNI_FMT("HIT BAD TRAP", ASNI_FG_RED))),npc_state.halt_pc);
#ifdef IRINGBUF
		if(npc_state.state == NEMU_ABORT){
	 	printf("Iringbuf at gr_inst:%ld\n", g_nr_guest_inst);
      	 	if(iringbuf.index<IRINGBUF_LEN){
		 for(int p=0;p<iringbuf.index-1;p++)   printf("\t%s \n",iringbuf.buf[p]);
		 printf("  ----> %s\n",iringbuf.buf[iringbuf.index-1]);	
	 	 }
         	else{
		 for(int p=0;p<IRINGBUF_LEN-1;p++) printf("\t%s \n",iringbuf.buf[p]);
		 printf("  ----> %s \n",iringbuf.buf[63]);  
	 	 }
		}	
#endif
		sim_exit();

		case NEMU_QUIT:printf("Iringbuf at gr_inst:%ld\n", g_nr_guest_inst);
statistic();
	}
}


void assert_fail_msg() {
  isa_reg_display();
  //statistic();
#ifdef IRINGBUF
	 	printf("Iringbuf at gr_inst:%ld\n", g_nr_guest_inst);
      	 	if(iringbuf.index<IRINGBUF_LEN){
		 for(int p=0;p<iringbuf.index-1;p++)   printf("\t%s \n",iringbuf.buf[p]);
		 printf("  ----> %s\n",iringbuf.buf[iringbuf.index-1]);	
	 	 }
         	else{
		 for(int p=0;p<IRINGBUF_LEN-1;p++) printf("\t%s \n",iringbuf.buf[p]);
		 printf("  ----> %s \n",iringbuf.buf[63]);  
	 	 }
#endif

}
